package com.siteseat.crudeq.kernel.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;

import com.siteseat.common.foundation.PropertyHelper;
import com.siteseat.common.foundation.Rater;
import com.siteseat.common.misc.SizeUnit;
import com.siteseat.crudeq.kernel.KernelComponent;
import com.siteseat.crudeq.kernel.KernelFactory;
import com.siteseat.crudeq.kernel.Queue;
import com.siteseat.crudeq.kernel.QueueListenerManager;
import com.siteseat.crudeq.kernel.QueueManager;
import com.siteseat.crudeq.kernel.QueueProfile;
import com.siteseat.crudeq.kernel.exception.DeployException;
import com.siteseat.crudeq.kernel.scheduler.MoveScheduler;
import com.siteseat.crudeq.kernel.scheduler.SaveScheduler;

/**
 *
 */
public class QueueManagerImpl extends KernelComponent implements QueueManager {
 
  private Map<Integer, Queue> mapById;
  
  private Map<String, Queue>  mapByName;

  private SaveScheduler       saveScheduler;

  private MoveScheduler       moveScheduler;
  
  private QueueListenerManager listenerManager;
  
  /**
   * {@inheritDoc}
   */
  @Override
  public QueueListenerManager getListenerManager() {
    return listenerManager;
  }

  /**
   * Sets the listenerManager
   *
   * @param listenerManager the listenerManager to set
   */
  public void setListenerManager( QueueListenerManager listenerManager ) {
    this.listenerManager = listenerManager;
  }

  /**
   * Gets the moveScheduler
   * 
   * @return the moveScheduler
   */
  public MoveScheduler getMoveScheduler() {
    return moveScheduler;
  }

  /**
   * Sets the moveScheduler
   * 
   * @param moveScheduler
   *          the moveScheduler to set
   */
  public void setMoveScheduler( MoveScheduler moveScheduler ) {
    this.moveScheduler = moveScheduler;
  }

  /**
   * Gets the saveScheduler
   * 
   * @return the saveScheduler
   */
  public SaveScheduler getSaveScheduler() {
    return saveScheduler;
  }

  /**
   * Sets the saveScheduler
   * 
   * @param saveScheduler
   *          the saveScheduler to set
   */
  public void setSaveScheduler( SaveScheduler saveScheduler ) {
    this.saveScheduler = saveScheduler;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doInit() throws Exception {
    super.doInit();
    
    this.mapById = new ConcurrentHashMap<Integer, Queue>();
    this.mapByName = new ConcurrentHashMap<String, Queue>();

    if( this.saveScheduler == null ) {
      this.saveScheduler = new SaveScheduler();
      this.saveScheduler.setIntervalByMilliseconds( 1 * 1000 );
      this.saveScheduler.init();
    }
    PropertyHelper.ensureInitialized( log, this.saveScheduler, "saveScheduler" );

    if( this.moveScheduler == null ) {
      this.moveScheduler = new MoveScheduler();
      this.moveScheduler.setIntervalByMilliseconds( 3600 * 1000 );
      this.moveScheduler.init();
    }
    PropertyHelper.ensureInitialized( log, this.moveScheduler, "moveScheduler" );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Queue ensureDeployed( Integer qid ) throws DeployException {
    if( qid == null ) {
      throw new DeployException( "Queue Id cannot be null" );
    }
    Queue r = this.mapById.get( qid );
    if( r == null ) {
      throw new DeployException( "Queue <" + qid + "> is not deployed yet" );
    }
    return r;
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public Queue ensureDeployed( String name ) throws DeployException {
    if( StringUtils.isBlank( name ) ) {
      throw new DeployException( "Queue name cannot be null" );
    }
    Queue r = this.mapByName.get( name );
    if( r == null ) {
      throw new DeployException( "Queue <" + name + "> is not deployed yet" );
    }
    return r;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void ensureNotDeployed( Integer qid ) throws DeployException {
    if( qid == null ) {
      throw new DeployException( "Queue Id cannot be null" );
    }
    if( this.mapById.containsKey( qid ) ) {
      throw new DeployException( "Queue <" + qid + "> is already deployed" );
    }
  }
  

  /**
   * {@inheritDoc}
   */
  @Override
  public void ensureNotDeployed( String name ) throws DeployException {
    if( StringUtils.isBlank( name ) ) {
      throw new DeployException( "Queue name cannot be null" );
    }
    if( this.mapByName.containsKey( name ) ) {
      throw new DeployException( "Queue <" + name + "> is already deployed" );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void deploy( String actor, QueueProfile profile ) throws DeployException {
    ensureNotDeployed( profile.getId() );
    ensureNotDeployed( profile.getName() );

    KernelFactory f = getFactory();
    
    Queue queue;
    try {      
      PersistedChunkManager mgr = f.newPersistedChunkManager( profile );
      mgr.init();
      
      queue = f.newQueue( profile, mgr );
      queue.init();
      
      saveScheduler.addManager( mgr );
      moveScheduler.addManager( mgr );

      this.mapById.put( profile.getId(), queue );
      this.mapByName.put( profile.getName(), queue );
      
      queue.setListener( this.listenerManager );
    } catch( Exception e ) {
      throw new DeployException( e );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDeployed( Integer qid ) {
    return this.mapById.containsKey( qid );
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isDeployed( String name ) {
    return this.mapByName.containsKey( name );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void undeploy( String actor, Integer qid ) throws DeployException {
    Queue q = ensureDeployed( qid );

    try {
      q.setListener( null );

      this.mapByName.remove( q.getProfile().getName() );
      this.mapById.remove( qid );
      
      saveScheduler.removeManager( qid );
      moveScheduler.removeManager( qid );

      q.destroy();
    } catch( Exception e ) {
      throw new DeployException( e );
    }
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public void undeploy( String actor, String name ) throws DeployException {
    Queue q = ensureDeployed( name );
    undeploy( actor, q.getProfile().getId() );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void doDestroy() throws Exception {

    List<Queue> queues = new ArrayList<Queue>( this.mapById.values() );

    for( Integer qid: this.mapById.keySet() ) {
      try {
        undeploy( null, qid );
      } catch( Exception e ) {
        log.error( "Failed to undeploy queue <" + qid + "> due to:", e );
      }
    }

    reportAll( queues );

    this.mapById.clear();
    this.mapByName.clear();
  }

  /**
   * 
   */
  protected void reportAll( Collection<Queue> queues ) {
    Rater sr = new Rater( SizeUnit.MB, TimeUnit.SECONDS, "Save Rater (Total)" );
    for( Queue q: queues ) {
      QueueProfile p = q.getProfile();
      sr.mergeWith( p.getSaveRater() );
    }

    log.info( sr.reportAll( "\n" ) );
  }

  /**
   * 
   */
  public void reportAll() {
    reportAll( this.mapById.values() );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Queue find( Integer qid ) {
    return this.mapById.get( qid );
  }
  
  /**
   * {@inheritDoc}
   */
  @Override
  public Queue find( String queueName ) {
    return this.mapByName.get( queueName );
  }

}
